home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 8: LINUX Games
/
Linux Cubed Series 8 - LINUX Games.iso
/
games
/
x11
/
rpg
/
crossfir.92
/
crossfir
/
crossfire-0.92.5
/
server
/
skill_util.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-07-24
|
48KB
|
1,516 lines
/* Created July 95 to separate skill utilities from actual skills -b.t. */
/* Reconfigured skills code to allow linking of skills to experience
* categories. This is done solely through the init_new_exp_system() fctn.
* June/July 1995 -b.t. thomas@astro.psu.edu
*/
/* July 1995 - Initial associated skills coding. Experience gains
* come solely from the use of skills. Overcoming an opponent (in combat,
* finding/disarming a trap, stealing from somebeing, etc) gains
* experience. Calc_skill_exp() handles the gained experience using
* modifications in the skills[] table. - b.t.
*/
/* define the following for skills utility debuging */
/* #define SKILL_UTIL_DEBUG */
#include <global.h>
#include <object.h>
#ifndef __CEXTRACT__
#include <sproto.h>
#endif
#include <living.h> /* for defs of STR,CON,DEX,etc. -b.t.*/
#include <skills.h>
#include <skillist.h>
#include <spells.h>
/* table for stat modification of exp */
float stat_exp_mult[MAX_STAT+1]={
0.0,
0.00, 0.01, 0.03, 0.05, /* 1 - 4 */
0.20, 0.35, 0.5, /* 5 - 7 */
0.65, 0.7, 0.75, 0.8, /* 8 - 11 */
0.85, 0.9, 0.95, /* 12 - 14 */
1.0, 1.05, 1.10, 1.15, 1.20, /* 15 - 19 */
1.3, 1.4, 1.5, 1.6, 1.7, /* 20 - 24 */
2.0, 2.2, 2.4, 2.6, 2.8, 3.0 /* 25 - 30 */
};
/* do_skill() - Main skills use function-similar in scope to cast_spell().
* We handle all requests for skill use outside of some combat here.
* We require a separate routine outside of fire() so as to allow monsters
* to utilize skills. Success should be the amount of experience gained
* from the activity. Also, any skills that monster will use, will need
* to return a positive value of success if performed correctly. Usually
* this is handled in calc_skill_exp(). Thus failed skill use re-
* sults in a 0, or false value of 'success'.
* - b.t. thomas@astro.psu.edu
*/
int do_skill (object *op, int dir, char *params) {
int success=0; /* needed for monster_skill_use() too */
int skill = op->chosen_skill->stats.sp;
switch(skill) {
case SK_STEALING:
success = steal(op, dir);
break;
case SK_LOCKPICKING:
success = pick_lock(op, dir);
break;
case SK_HIDING:
success = hide(op);
break;
case SK_JUMPING:
success = jump(op, dir);
break;
case SK_INSCRIPTION:
success = write_on_item(op,params);
break;
case SK_MEDITATION:
meditate(op);
break;
/* note that the following 'attack' skills gain exp through hit_player() */
case SK_KARATE:
(void) attack_hth(op,dir,"karate-chopped");
break;
case SK_BOXING:
(void) attack_hth(op,dir,"punched");
break;
case SK_FLAME_TOUCH:
(void) attack_hth(op,dir,"flamed");
break;
case SK_CLAWING:
(void) attack_hth(op,dir,"clawed");
break;
case SK_MELEE_WEAPON:
(void) attack_melee_weapon(op,dir,NULL);
break;
case SK_FIND_TRAPS:
success = find_traps(op);
break;
case SK_MUSIC:
success = singing(op,dir);
break;
case SK_ORATORY:
success = use_oratory(op,dir);
break;
case SK_SMITH:
case SK_BOWYER:
case SK_JEWELER:
case SK_ALCHEMY:
case SK_THAUMATURGY:
case SK_LITERACY:
case SK_DET_MAGIC:
case SK_DET_CURSE:
case SK_WOODSMAN:
success = skill_ident(op);
break;
case SK_REMOVE_TRAP:
success = remove_trap(op,dir);
break;
case SK_SET_TRAP:
case SK_THROWING:
new_draw_info(NDI_UNIQUE, 0,op,"This skill is not currently implemented.");
break;
case SK_USE_MAGIC_ITEM:
case SK_MISSILE_WEAPON:
new_draw_info(NDI_UNIQUE, 0,op,"There is no special attack for this skill.");
break;
case SK_PRAYING:
success = pray(op);
break;
case SK_SPELL_CASTING:
case SK_CLIMBING:
case SK_BARGAINING:
new_draw_info(NDI_UNIQUE, 0,op,"This skill is already in effect.");
break;
default:
LOG(llevDebug,"%s attempted to use unknown skill: %d\n"
,query_name(op), op->chosen_skill->stats.sp);
break;
}
/* For players we now update the speed_left from using the skill.
* Monsters have no skill use time because of the random nature in
* which use_monster_skill is called already simulates this. -b.t.
*/
if(op->type==PLAYER) op->speed_left -= get_skill_time(op,skill);
/* this is a good place to add experience for successfull use of skills.
* Note that add_exp() will figure out player/monster experience
* gain problems.
*/
if(success&&skills[skill].category!=EXP_NONE) add_exp(op,success);
return success;
}
/* calc_skill_exp() - calculates amount of experience can be gained for
* successfull use of a skill. Returns value of experience gain.
* Here we take the view that a player must 'overcome an opponent'
* in order to gain experience. Examples include foes killed combat,
* finding/disarming a trap, stealing from somebeing, etc.
* The gained experience is based primarily on the difference in levels,
* exp point value of vanquished foe, the relevent stats of the skill being
* used and modifications in the skills[] table.
*
* For now, monsters and players will be treated differently. Below I give
* the algorithm for *PLAYER* experience gain. Monster exp gain is simpler.
* Monsters just get 10% of the exp of the opponent.
*
* Example: the basic exp gain for player 'who' how "vanquished" oppoenent
* 'op' using skill 'sk' is:
*
* EXP GAIN = (op->exp + skills[sk].bexp) * lvl_mult * stat_mult
*
* where lvl_mult is
*
* for(pl->level < op->level)::
* lvl_mult = skills[sk].lexp * (op->level - pl->level)
* for(pl->level = op->level)::
* lvl_mult = skills[sk].lexp;
* for(pl->level > op->level)::
* lvl_mult = op_lvl/pl_lvl;
*
* and stat_mult is taken from stat_exp_mult[] table above.
*
* Coded by b.t. thomas@astro.psu.edu
*/
int calc_skill_exp(object *who, object *op) {
int who_lvl= SK_level(who);
int sk,op_exp=0,op_lvl= 0;
float base,value,stat_mult=1.0,lvl_mult=0.0;
/* sometimes we dont have a specific opponent to 'overcome'.
* 'hiding' is just such a case. For these skills, our
* 'opponent is the map we are on. Thus experience
* gain is just skills[sk].bexp, and modified by the
* map difficulty instead of an opponent SK_level.
* In the case that map difficulty =< player then op_lvl = 0
*/
/* Oct 95 - where we have an object, I expanded our treatment
* to 3 cases:
* non-living magic obj, runes and everything else.
*
* If an object is not alive and magical we set the base exp higher to
* help out exp awards for skill_ident skills. Also, if
* an item is type RUNE, we give out exp based on stats.Cha
* and level (this was the old system) -b.t.
*/
if(!op) { /* no item/creature */
op_lvl= who->map->difficulty < 1 ? 1: who->map->difficulty;
op_exp = 0;
} else if(op->type==RUNE) { /* all traps. If stats.Cha > 1 we use that
* for the amount of experience */
op_exp = op->stats.Cha>1 ? op->stats.Cha : op->stats.exp;
op_lvl = op->level;
} else { /* all other items/living creatures */
op_exp = op->stats.exp;
if(!QUERY_FLAG(op,FLAG_ALIVE)) { /* for ident/make items */
op_lvl = op->level;
op_lvl += op->magic ? 0 : (5 * op->magic);
} else /* its a monster, grab its level */
op_lvl= SK_level(op);
}
if(op_lvl<1) op_lvl = 1;
if(who->type!=PLAYER) { /* for monsters only */
return ((int) (op_exp*0.1)+1); /* we add one to insure positive value is returned */
} else { /* for players */
/* FIRST, what skill are we using? */
if(who->chosen_skill==NULL) {
LOG(llevError,"Bad call of calc_skill_exp() by player. \n");
return 0;
} else
sk = who->chosen_skill->stats.sp;
base = op_exp + skills[sk].bexp; /* get base exp */
/* get level multiplier */
if(who_lvl < op_lvl)
lvl_mult=(float) skills[sk].lexp * (float) ((float) op_lvl - (float) who_lvl);
else if(who_lvl == op_lvl)
lvl_mult = (float) skills[sk].lexp;
else
lvl_mult = ((float) ((float) op_lvl)/((float) who_lvl));
stat_mult = calc_stat_mult(who,sk);
}
/* assemble the exp total, and return value */
value = base * (lvl_mult * stat_mult);
#ifdef SKILL_UTIL_DEBUG
LOG(llevDebug,"calc_skill_exp(): who: %s(lvl:%d) op:%s(lvl:%d)\n",
who->name,who_lvl,op->name,op_lvl);
LOG(llevDebug,"%s(%s): base_exp=%f lvl_mult=%f stat_mult=%f value=%f\n",
who->name,skills[sk].name,base,lvl_mult,stat_mult,value);
#endif
return ( (int) value);
}
/* calc_stat_mult() - figures a factor to modify gained experience by
* based on the sum of relvant stats of skill 'sk' being used by 'who'.
*/
float calc_stat_mult(object *who,int sk) {
int sum;
float factor = 1.0;
sum = get_weighted_skill_stat_sum (who,sk);
/* look up factor for weighted sum */
if(sum <MAX_STAT) factor = stat_exp_mult[sum];
return factor;
}
/* find relevant stats or a skill then return their weighted sum.
* I admit the calculation is done in a retarded way.
* If stat1==NO_STAT this isnt an associated stat. Returns
* zero then. -b.t.
*/
int get_weighted_skill_stat_sum ( object *who, int sk) {
float sum;
int number = 1;
if(skills[sk].stat1==NO_STAT) {
return 0;
} else
sum = get_attr_value(&(who->stats),skills[sk].stat1);
if(skills[sk].stat2!=NO_STAT) {
sum += get_attr_value(&(who->stats),skills[sk].stat2);
number++;
}
if(skills[sk].stat3!=NO_STAT) {
sum += get_attr_value(&(who->stats),skills[sk].stat3);
number++;
}
return ((int) sum/number);
}
/* init_new_exp_system() - called in init(). This function will reconfigure
* the properties of skills array and experience categories, and links
* each skill to the appropriate experience category
* -b.t. thomas@astro.psu.edu
*/
void init_new_exp_system() {
static int init_new_exp_done = 0;
int i;
if (init_new_exp_done)
return;
init_new_exp_done = 1;
init_exp_obj(); /* locate the experience objects and create list of them */
link_skills_to_exp(); /* link skills to exp cat, based on shared stats */
(void) read_skill_params(); /* overide skills[] w/ values from skill_params */
#ifdef DUMP_SWITCHES
if (dump_monsters == 5) {
char buf[MAX_BUF];
fprintf(stderr, "\n");
fprintf(stderr, "exper_catgry \t str \t dex \t con \t wis \t cha \t int \t pow \n");
for (i = 0; i < nrofexpcat; i++)
fprintf(stderr, "%d-%s \t %d \t %d \t %d \t %d \t %d \t %d \t %d \n",
i,exp_cat[i]->name,
exp_cat[i]->stats.Str,exp_cat[i]->stats.Dex,exp_cat[i]->stats.Con,
exp_cat[i]->stats.Wis,exp_cat[i]->stats.Cha,exp_cat[i]->stats.Int,
exp_cat[i]->stats.Pow);
fprintf(stderr, "\n");
sprintf(buf,"%20s %12s %4s %4s %4s %5s %5s %5s\n",
"sk# Skill name","ExpCat","Time","Base","xlvl","Stat1","Stat2","Stat3");
fprintf(stderr, buf);
sprintf(buf,"%20s %12s %4s %4s %4s %5s %5s %5s\n",
"--- ----------","------","----","----","----","-----","-----","-----");
fprintf(stderr, buf);
for (i = 0; i < NROFSKILLS; i++) {
sprintf(buf,"%2d-%17s %12s %4d %4ld %4g %5s %5s %5s\n", i, skills[i].name,
exp_cat[skills[i].category]!=NULL?exp_cat[skills[i].category]->name:"NONE",
skills[i].time,
skills[i].bexp,
skills[i].lexp,
skills[i].stat1!= NO_STAT ? short_stat_name[skills[i].stat1]: "---",
skills[i].stat2!= NO_STAT ? short_stat_name[skills[i].stat2]: "---",
skills[i].stat3!= NO_STAT ? short_stat_name[skills[i].stat3]: "---");
fprintf(stderr, buf);
}
exit(0);
}
#endif
}
/* init_exp_obj() - this routine looks through all the assembled
* archetypes for experience objects then copies their values into
* the exp_cat[] array. - bt.
*/
void init_exp_obj() {
archetype *at;
nrofexpcat = 0;
for(at=first_archetype;at!=NULL;at=at->next)
if(at->clone.type==EXPERIENCE) {
exp_cat[nrofexpcat] = get_object() ;
exp_cat[nrofexpcat]->level = 1;
exp_cat[nrofexpcat]->stats.exp = 0;
copy_object(&at->clone, exp_cat[nrofexpcat]);
nrofexpcat++;
if(nrofexpcat == MAX_EXP_CAT) {
LOG(llevError,"Aborting! Reached limit of available experience\n");
LOG(llevError,"categories. Need to increase value of MAX_EXP_CAT.\n");
exit (0);
}
}
if(nrofexpcat == 0) {
LOG(llevError,"Aborting! No experience objects found in archetypes.\n");
exit (0);
}
}
/* link_skills_to_exp() - this links the skills in skills[] to the appropriate
* experience category. Linking is based only on the stats (1,2,3) of the
* skill. If none of the stats assoc. w/ a skill match those in any experience
* object then the skill becomes 'miscellaneous' -b.t.
*/
void link_skills_to_exp() {
int i=0,j=0;
for(i=0;i<NROFSKILLS;i++)
for(j=0;j<nrofexpcat;j++)
if(check_link(skills[i].stat1,exp_cat[j])) {
skills[i].category = j;
continue;
} else if(skills[i].category == EXP_NONE
&& check_link(skills[i].stat2,exp_cat[j])) {
skills[i].category = j;
continue;
} else if(skills[i].category == EXP_NONE
&& check_link(skills[i].stat3,exp_cat[j])) {
skills[i].category = j;
continue;
/* failed to link, set to EXP_NONE */
} else if (j==nrofexpcat || skills[i].stat1 == NO_STAT) {
skills[i].category = EXP_NONE;
continue;
}
}
/* check_link() - this returns true if the specified stat is set in the
* experience object. Wish this was more 'generalized'. Added new stats
* will cause this routine to abort CF. - b.t.
*/
int check_link (int stat, object *exp) {
switch (stat) {
case STR:
if(exp->stats.Str) return 1;
break;
case CON:
if(exp->stats.Con) return 1;
break;
case DEX:
if(exp->stats.Dex) return 1;
break;
case INT:
if(exp->stats.Int) return 1;
break;
case WIS:
if(exp->stats.Wis) return 1;
break;
case POW:
if(exp->stats.Pow) return 1;
break;
case CHA:
if(exp->stats.Cha) return 1;
break;
case NO_STAT:
return 0;
default:
LOG(llevError, "Aborting! Tried to link skill with unknown stat!\n");
exit (0);
}
return 0;
}
/* read_skill_params() - based on init_spell_params(). This
* function should read a file 'skill_params' in the /lib
* directory. -b.t.
*
* format of the file 'skill_params' is:
* name
* EXP_CAT, bexp, lexp, stat1, stat2, stat3
*
* see the values in lib/skill_params for more inspiration/direction
*/
void read_skill_params () {
FILE *skill_params;
char fname[MAX_BUF];
char skill_name[256];
char skill_attrib[256];
int cat,bexp,time,stat1,stat2,stat3,skillindex;
float lexp;
sprintf(fname,"%s/%s",LibDir,"skill_params");
if(! (skill_params=fopen(fname,"r")))
{
perror(fname);
return;
}
while(!feof(skill_params))
{
/* Large buf, so that long comments don't kill it. */
fgets(skill_name,255,skill_params);
if (*skill_name=='#') continue;
skillindex=lookup_skill_by_name(skill_name);
if(skillindex == -1) {
LOG(llevError,"\nskill_params has unrecognized skill: %s",skill_name);
continue;
}
fgets(skill_attrib,255,skill_params);
sscanf(skill_attrib,"%d %d %d %f %d %d %d",
&cat,&time,&bexp,&lexp,&stat1,&stat2,&stat3);
skills[skillindex].category=cat;
skills[skillindex].time=time;
skills[skillindex].bexp=bexp;
skills[skillindex].lexp=lexp;
skills[skillindex].stat1=stat1;
skills[skillindex].stat2=stat2;
skills[skillindex].stat3=stat3;
}
}
/* lookup_skill_by_name() - based on look_up_spell_by_name - b.t. */
int lookup_skill_by_name(char *string) {
int skillnr=0, nmlen;
char name[MAX_BUF];
if(!string) return -1;
/* truncate string at first blank space to find skill name. */
nmlen = strcspn(string," ");
strncpy(name,string,nmlen);
while(strncmp(name,skills[skillnr].name,MIN(strlen(skills[skillnr].name),
nmlen)) && skillnr < (NROFSKILLS-1) )
{
skillnr++;
}
if(skillnr==NROFSKILLS) skillnr=-1;
return skillnr;
}
/* check_skill_to_fire() - */
int check_skill_to_fire(object *who) {
int skillnr=0;
rangetype shoottype=range_none;
if(who->type!=PLAYER) return 1;
switch((shoottype = who->contr->shoottype)) {
case range_bow:
skillnr = SK_MISSILE_WEAPON;
break;
case range_none:
case range_skill:
return 1;
break;
case range_magic:
if(spells[who->contr->chosen_spell].cleric)
skillnr = SK_PRAYING;
else
skillnr = SK_SPELL_CASTING;
break;
case range_scroll:
case range_rod:
case range_horn:
case range_wand:
skillnr = SK_USE_MAGIC_ITEM;
break;
default:
LOG(llevError,"Warning: bad call of check_skill_to_fire()\n");
return 0;
}
if (change_skill(who,skillnr)) {
#ifdef SKILL_UTIL_DEBUG
LOG(llevDebug,"check_skill_to_fire(): got skill:%s for %s\n"
,skills[skillnr].name,who->name);
#endif
who->contr->shoottype=shoottype;
return 1;
} else
return 0;
}
/* check_skill_to_apply() - When a player tries to use an
* object which requires a skill this function is called.
* (examples are weapons like swords and bows)
* It does 2 things: checks for appropriate skill in player inventory
* and alters the player status by making the appropriate skill
* the new chosen_skill. This routine works for both players
* applying and unapplying objects. Returns true if the skill exists
* and could be readied or we successfully unapplyied an object.
* -bt. thomas@astro.psu.edu
*/
/* IMPORTANT NOTE: always call this fctn *after* FLAG_APPLY is set/unset
* and *before* change_abil() is called.
*/
int check_skill_to_apply(object *who, object *item) {
int skill=0;
rangetype shoottype=range_none;
if(who->type!=PLAYER) return 1; /* this fctn only for players */
/* first figure out the required skill and ending shoottype
* from the item */
switch(item->type) {
case WEAPON:
skill = SK_MELEE_WEAPON;
shoottype = range_skill;
break;
case BOW:
skill = SK_MISSILE_WEAPON;
shoottype = range_bow;
break;
case POTION:
case SCROLL:
skill = SK_USE_MAGIC_ITEM; /* not literacy! */
shoottype = range_scroll;
break;
case ROD:
skill = SK_USE_MAGIC_ITEM;
shoottype = range_rod;
break;
case WAND:
skill = SK_USE_MAGIC_ITEM;
shoottype = range_wand;
break;
case HORN:
skill = SK_USE_MAGIC_ITEM;
shoottype = range_horn;
break;
default:
LOG(llevDebug,"Warning: bad call of check_skill_to_apply()");
LOG(llevDebug,"No skill exists for item: %s",query_name(item));
return 0;
}
/* now we alter player status appropriately */
if(!QUERY_FLAG(item,FLAG_APPLIED)) { /* we are trying to unapply */
if(who->chosen_skill && who->chosen_skill->stats.sp == skill)
(void) change_skill(who,-1);
return 0;
} else { /* we are trying to apply */
if(!who->chosen_skill || (who->chosen_skill
&& who->chosen_skill->stats.sp!=skill))
if(!change_skill(who,skill)) {
new_draw_info(NDI_UNIQUE, 0,who,"You can't use that!");
CLEAR_FLAG(item,FLAG_APPLIED);
return 0;
}
}
who->contr->shoottype=shoottype;
return 1;
}
/* init_player_exp() - makes various checks and initialization of player
* experience objects. If things aren't cool then we change them here.
* -b.t.
*/
int init_player_exp(object *pl) {
int i,j,exp_index=0;
object *tmp,*exp_ob[MAX_EXP_CAT];
if(pl->type!=PLAYER) {
LOG(llevError, "init_player_exp(): called non-player %s.\n",pl->name);
return 0;
}
/* first-pass find all current exp objects */
for(tmp=pl->inv;tmp;tmp=tmp->below)
if(tmp->type==EXPERIENCE) {
exp_ob[exp_index] = tmp;
exp_index++;
} else if (exp_index == MAX_EXP_CAT)
return 0;
/* second - if pl has wrong nrof experience objects
* then give player a complete roster.
*/
/* No exp objects - situation for a new player
* or pre-skills/exp code player file */
if(!exp_index) {
for(j=0;j<nrofexpcat;j++) {
tmp = get_object();
copy_object(exp_cat[j],tmp);
insert_ob_in_ob(tmp,pl);
tmp->stats.exp = pl->stats.exp/nrofexpcat;
exp_ob[j] = tmp;
if (pl->contr->eric_server > 0)
esrv_send_item(pl, tmp);
exp_index++;
}
} else if (exp_index!=nrofexpcat) { /* situation for old save file */
if(exp_index<nrofexpcat)
LOG(llevError,"init_player_exp(): player has too few experience objects.\n");
if(exp_index>nrofexpcat)
LOG(llevError,"init_player_exp(): player has too many experience objects.\n");
for(j=0;j<nrofexpcat;j++)
for(i=0;i<=exp_index;i++)
if(!strcmp(exp_ob[i]->name,exp_cat[j]->name)) {
break;
} else if(i==exp_index) {
tmp = get_object();
copy_object(exp_cat[j],tmp);
insert_ob_in_ob(tmp,pl);
exp_ob[i] = tmp;
if (pl->contr->eric_server > 0)
esrv_send_item(pl, tmp);
exp_index++;
}
}
/* now we loop through one more time and set "apply"
* flag on valid expericence objects. Fix_player()
* requires this, and we get the bonus of being able
* to ignore invalid experience objects in the player
* inventory (player file from another game set up?).
* Also, we reset the score (or "total" experience) of
* the player to be the sum of all valid experience
* objects.
*/
pl->stats.exp = 0;
for(i=0;i<exp_index;i++) {
if(!QUERY_FLAG(exp_ob[i],FLAG_APPLIED))
SET_FLAG(exp_ob[i],FLAG_APPLIED);
pl->stats.exp += exp_ob[i]->stats.exp;
player_lvl_adj(NULL, exp_ob[i]);
}
return 1;
}
/* unlink_skill() - removes skill from a player skill list and
* unlinks the pointer to the exp object */
void unlink_skill(object *skillop) {
object *op=skillop?skillop->env:NULL;
if(!op||op->type!=PLAYER) {
LOG(llevError,"Error: unlink_skill() called for non-player!");
return;
}
#ifdef LINKED_SKILL_LIST
if(remove_skill_from_list(op,skillop))
#endif
skillop->exp_obj = NULL;
}
int remove_skill_from_list(object *op, object *skillop) {
objectlink *obl=op->sk_list,*first,*prev=NULL,*nxt;
if(!skillop) return 0;
prev=first=obl;
while(obl) {
nxt = obl->next ? obl->next: NULL;
if(obl->id==skillop->stats.sp) {
if(obl==first) {
op->sk_list = nxt;
nxt = (nxt&&nxt->next) ? nxt->next: NULL;
}
if(prev) prev->next = nxt;
#if 0
LOG(llevDebug,"Removing skill: %s from list\n",
skillop->name);
#endif
CFREE(obl);
obl = NULL;
return 1;
} else
prev = obl;
obl=nxt;
}
if(first) op->sk_list = first;
return 0;
}
/* link_player_skills() - linking skills with experience objects
* and creating a linked list of skills for later fast access.
*
* Returns true if successfull. To be usefull this routine has to
* be called *after* init_player_exp() and give_initial_items()
*
* Aug 95 added feature whereby old player save files will get a
* (needed) basic assortment of skills (see basic_skill array).
*
* - b.t. thomas@astro.psu.edu
*/
int link_player_skills(object *pl) {
archetype *at=NULL;
int i,j,cat=0,sk_index=0,exp_index=0,old_file=1;
object *tmp,*sk_ob[100],*exp_ob[MAX_EXP_CAT];
static char *basic_skills[] = { "skill_melee_weapon",
"skill_missile_weapon",
"skill_use_magic_item",
"skill_praying",
"skill_spellcasting",
"skill_find_traps",
"skill_remove_trap"};
/* first find all exp and skill objects */
for(tmp=pl->inv;tmp&&sk_index<100;tmp=tmp->below)
if(tmp->type==EXPERIENCE) {
exp_ob[exp_index] = tmp;
exp_index++;
} else if (tmp->type==SKILL) {
sk_ob[sk_index]=tmp;
sk_index++;
if(!strcmp(tmp->name,skills[SK_USE_MAGIC_ITEM].name)) old_file = 0;
}
if(exp_index!=nrofexpcat) {
LOG(llevError,"link_player_skills() - player has bad number of exp obj\n");
if(!init_player_exp(pl)) {
LOG(llevError,"link_player_skills() - failed to correct problem.\n");
return 0;
}
(void) link_player_skills(pl);
return 1;
}
/* This looks like we have an old player file, so give those basic
* skills to the player in this case.
*/
if(old_file) {
object *tmp2;
int limit = sizeof(basic_skills)/sizeof(char *);
/* this is solely for old characters */
if(pl->contr->orig_stats.Pow==0) {
pl->stats.Pow = pl->stats.Int;
pl->contr->orig_stats.Pow = pl->contr->orig_stats.Int;
}
for(i=0;i<limit;i++)
if((at=find_archetype(basic_skills[i]))!=NULL) {
int add = 1;
tmp = arch_to_object(at);
/* check if they already have this (basic) skill */
for(tmp2=pl->inv;tmp2;tmp2=tmp2->below)
if(tmp2->type==SKILL && !strcmp(tmp2->name,tmp->name)) {
add = 0;
break;
}
if(add) {
insert_ob_in_ob(tmp,pl);
sk_ob[sk_index] = tmp;
sk_index++;
LOG(llevDebug,"Added basic skill: %s to inventory of %s\n",
basic_skills[i], pl->name);
}
} else {
LOG(llevError,
"init_player_skills() - can't find basic skill: %s\n",basic_skills[i]);
return 0;
}
}
/* Ok, create linked list and link the associated skills to exp objects */
for(i=0;i<sk_index;i++) {
#ifdef LINKED_SKILL_LIST
objectlink *obl;
obl = (objectlink *) malloc(sizeof(objectlink));
obl->ob=sk_ob[i];
obl->id=sk_ob[i]->stats.sp;
obl->next = pl->sk_list;
pl->sk_list = obl;
#endif
cat = skills[sk_ob[i]->stats.sp].category;
if(cat==EXP_NONE) continue;
for(j=0;exp_ob[j]!=NULL&&j<exp_index;j++)
if(!strcmp(exp_cat[cat]->name,exp_ob[j]->name)) break;
sk_ob[i]->exp_obj = exp_ob[j];
}
return 1;
}
/* link_player_skill() - links a skill to exp object when applied or learned by
* a player. Returns true if can link. Returns false if got misc
* skill - bt.
*/
int link_player_skill(object *pl, object *skillop) {
object *tmp;
int cat;
#ifdef LINKED_SKILL_LIST
objectlink *obl;
/* add it to the linked list */
obl = (objectlink *) malloc(sizeof(objectlink));
obl->ob=skillop;
obl->id=skillop->stats.sp;
obl->next = pl->sk_list;
pl->sk_list = obl;
#endif
cat = skills[skillop->stats.sp].category;
if(cat!=EXP_NONE) { /* ok the skill has an exp object, now find right one in pl inv */
for(tmp=pl->inv;tmp;tmp=tmp->below)
if(tmp->type==EXPERIENCE&&!strcmp(exp_cat[cat]->name,tmp->name)) {
skillop->exp_obj = tmp;
break;
}
return 1;
}
skillop->exp_obj = NULL;
return 0;
}
/* Learn skill. This inserts the requested skill in the player's
* inventory. The 'slaying' field of the scroll should have the
* exact name of the requested archetype (there should be a better way!?)
* -bt. thomas@nomad.astro.psu.edu
*/
int
learn_skill (object *pl, object *scroll) {
object *tmp;
archetype *skill;
#ifdef LINKED_SKILL_LIST
objectlink *obl;
#else
object *tmp2;
#endif
skill=find_archetype(scroll->slaying);
if(!skill)
return 2;
tmp=arch_to_object(skill);
if(!tmp) return 2;
/* check if player already has it */
#ifdef LINKED_SKILL_LIST
for(obl=pl->sk_list;obl;obl=obl->next)
if(obl->ob->invisible
&&(obl->ob->stats.sp==tmp->stats.sp)) return 0;
#else /* LINKED_SKILL_LIST */
for(tmp2=pl->inv;tmp2;tmp2=tmp2->below)
if(tmp2->type==SKILL&&tmp2->invisible
&&tmp2->stats.sp==tmp->stats.sp) return 0;
# endif /* LINKED_SKILL_LIST */
/* now a random change to learn, based on player Int */
if(RANDOM()%100>learn_spell[pl->stats.Int])
return 2; /* failure :< */
/* Everything is cool. Give'em the skill */
(void) insert_ob_in_ob(tmp,pl);
(void) link_player_skill(pl,tmp);
if (pl->contr->eric_server > 0)
esrv_send_item(pl, tmp);
return 1;
}
/* show_skills() - Meant to allow players to examine
* their current skill list. b.t. (thomas@nomad.astro.psu.edu)
* I have now added capability to show assoc. experience objects too.
* Inportant note: the value of chosen_skill->level is not always
* accurate--it is only updated in change_skill(), therefore long
* unused skills may have wrong value. This is why we call
* skill->exp_obj->level instead (the level of the associated experience
* object -- which is always right).
* -b.t.
*/
void show_skills(object *op) {
object *tmp=NULL;
char buf[MAX_BUF], *in;
int i,length,is_first;
#ifdef LINKED_SKILL_LIST
objectlink *obl;
if (!op->sk_list) {
new_draw_info(NDI_UNIQUE, 0,op,"You know no skills.");
op->chosen_skill=NULL; /* double safety - for those who lose skills */
return;
} else {
#endif
length = INFOCHARS-19;
in = "";
if (op)
clear_win_info(op);
/* sprintf(buf,"Player Skills Category /lvl"); */
sprintf(buf,"Player skills by experience category");
new_draw_info(NDI_UNIQUE, 0,op,buf);
#ifdef LINKED_SKILL_LIST
}
#endif
/* print out skills by category */
for (i=0;i<=nrofexpcat;i++) {
is_first=1;
if(i==nrofexpcat) i=EXP_NONE; /* skip to misc exp category */
#ifdef LINKED_SKILL_LIST
for (obl=op->sk_list;obl;obl=obl->next) {
tmp = obl->ob;
#else
for (tmp=op->inv;tmp;tmp=tmp->below) {
if(tmp->type!=SKILL) continue;
#endif
if(skills[tmp->stats.sp].category==i
&&(tmp->invisible||QUERY_FLAG(tmp,FLAG_APPLIED))) {
/* header info */
if(is_first) {
is_first=0;
new_draw_info(NDI_UNIQUE, 0,op," ");
if(tmp->exp_obj) {
object *tmp_exp = tmp->exp_obj;
int k=(length-15-strlen(tmp_exp->name));
char tmpbuf[40];
strcpy(tmpbuf,tmp_exp->name);
while(k>0) {k--; strcat(tmpbuf,".");}
new_draw_info_format(NDI_UNIQUE,0,op,"%slvl:%3d (xp:%d)",
tmpbuf,tmp_exp->level,tmp_exp->stats.exp);
} else if(i==EXP_NONE) {
new_draw_info(NDI_UNIQUE,0,op,"misc.");
}
}
/* print matched skills */
if((!op || QUERY_FLAG(op, FLAG_WIZ)))
(void) sprintf(buf,"%s%s %s (%5d)",in,
QUERY_FLAG(tmp,FLAG_APPLIED)?"*":"-",
skills[tmp->stats.sp].name,tmp->count);
else
(void) sprintf(buf,"%s%s %s",in,
QUERY_FLAG(tmp,FLAG_APPLIED)?"*":"-",
skills[tmp->stats.sp].name);
new_draw_info(NDI_UNIQUE,0,op,buf);
}
}
}
}
/* use_skill() - similar to invoke command, it executes the skill in the
* direction that the user is facing. Returns false if we are unable to
* change to the requested skill, or were unable to use the skill properly.
* -b.t.
*/
int use_skill(object *op, char *params) {
int skill=lookup_skill_by_name(params);
if(change_skill(op,skill))
if(do_skill(op,op->facing,params))
return 1;
return 0;
}
/* change_skill() - returns true if we are able to change to the requested
* skill. Ignore the 'pl' designation, this code is useful for players and
* monsters. -bt. thomas@astro.psu.edu
*/
/* Sept 95. Got rid of nasty strcmp calls in here -b.t.*/
/* Dec 95 - cleaned up the code a bit, change_skill now passes an indexed
* value for the skill rather than a character string. Added find_skill.
* -b.t. */
int change_skill(object *pl, int sk_index) {
object *tmp;
/* unapply old skill, do this before returning */
if(pl->chosen_skill) {
if(pl->chosen_skill->stats.sp==sk_index
&& QUERY_FLAG(pl->chosen_skill,FLAG_APPLIED)) { /* already have it prepared */
pl->contr->shoottype = range_skill;
draw_stats(pl);
return 1;
}
/* need to do because some skills set path_attuned, slaying, etc */
CLEAR_FLAG(pl->chosen_skill,FLAG_APPLIED);
(void) change_abil(pl,pl->chosen_skill);
if(QUERY_FLAG(pl,FLAG_READY_SKILL))
CLEAR_FLAG(pl,FLAG_READY_SKILL);
if(!pl->chosen_skill->invisible) /* its a tool, need to unlink it */
unlink_skill(pl->chosen_skill);
pl->chosen_skill = NULL;
}
if(sk_index<0) { /* bye,bye. We didn't request a valid skill */
new_draw_info(NDI_UNIQUE, 0,pl,"You can't use an non-existent skill!");
return 0;
}
/* ok, look for the requested skill */
if((tmp=find_skill(pl,sk_index))) { /* yes, pl does have named skill */
pl->chosen_skill = tmp;
/* Update skill level from exp obj */
if (tmp->exp_obj)
pl->chosen_skill->level=tmp->exp_obj->level;
if(!tmp->invisible&&!QUERY_FLAG(tmp,FLAG_APPLIED)) { /* for tools */
if(pl->type==PLAYER) {
new_draw_info_format(NDI_UNIQUE, 0, pl,
"You can now use the skill: %s.", skills[sk_index].name);
if(!tmp->exp_obj)
(void) link_player_skill(pl, tmp);
}
}
if(!QUERY_FLAG(tmp,FLAG_APPLIED))
SET_FLAG(tmp, FLAG_APPLIED);
if(!QUERY_FLAG(pl,FLAG_READY_SKILL))
SET_FLAG(pl,FLAG_READY_SKILL);
(void) change_abil(pl,tmp);
fix_player(pl);
if(pl->type==PLAYER) {
new_draw_info_format(NDI_UNIQUE, 0,pl,
"Readied skill: %s.",skills[tmp->stats.sp].name);
pl->contr->shoottype = range_skill;
draw_stats(pl);
draw_inventory(pl);
}
return 1;
}
if(QUERY_FLAG(pl,FLAG_READY_SKILL)) /* shouldnt happen -b.t. */
CLEAR_FLAG(pl, FLAG_READY_SKILL);
if(pl->type!=PLAYER) return 0;
/* Player stuff for when we didnt ready the requested skill. This can be
* either because we have requested a non-existent skill or because
* we selected negative skill index. The 'negative' option mostly occurs
* when change_skill is called to silently change player skill to NULL.
* For example, unwielding a weapon--change_skill() called from
* apply_special().
*/
if(sk_index>=0 && sk_index<NROFSKILLS)
new_draw_info_format(NDI_UNIQUE, 0,pl,"You have no knowledge of %s.",
skills[sk_index].name);
pl->contr->shoottype = range_none;
return 0;
}
/* attack_melee_weapon() - this handles melee weapon attacks -b.t.
* For now we are just checking to see if we have a ready weapon here.
* But there is a real neato possible feature of this scheme which
* bears mentioning:
* Since we are only calling this from do_skill() in the future
* we may make this routine handle 'special' melee weapons attacks
* (like disarming manuever with sai) based on player SK_level and
* weapon type.
*/
int attack_melee_weapon(object *op, int dir, char *string) {
if(!QUERY_FLAG(op, FLAG_READY_WEAPON)) {
if(op->type==PLAYER)
new_draw_info(NDI_UNIQUE, 0,op,"You have no ready weapon to attack with!");
return 0;
}
return skill_attack(NULL,op,dir,string);
}
/* attack_hth() - this handles all hand-to-hand attacks -b.t. */
/* July 5, 1995 - I broke up attack_hth() into 2 parts. In the first
* (attack_hth) we check for weapon use, etc in the second (the new
* function skill_attack() we actually attack.
*/
int attack_hth(object *pl, int dir, char *string) {
object *enemy=NULL,*weapon;
if(QUERY_FLAG(pl, FLAG_READY_WEAPON))
for(weapon=pl->inv;weapon;weapon=weapon->below) {
if(weapon->type!=WEAPON
||!QUERY_FLAG(weapon, FLAG_APPLIED)) continue;
CLEAR_FLAG(weapon,FLAG_APPLIED);
CLEAR_FLAG(pl,FLAG_READY_WEAPON);
if(pl->type==PLAYER) {
new_draw_info(NDI_UNIQUE, 0,pl,"You unweild your weapon in order to attack.");
fix_player(pl);
draw_stats(pl);
draw_inventory(pl);
}
break;
}
return skill_attack(enemy,pl,dir,string);
}
/* skill_attack() - Core routine for use when we attack using a skills
* system. There are'nt too many changes from before, basically this is
* a 'wrapper' for the old attack system. In essence, this code handles
* all skill-based attacks, ie hth, missile and melee weapons should be
* treated here. If an opponent is already supplied by move_player(),
* we move right onto do_skill_attack(), otherwise we find if an
* appropriate opponent exists.
*
* This is called by move_player() and attack_hth()
*
* Initial implementation by -bt thomas@astro.psu.edu
*/
int skill_attack (object *tmp, object *pl, int dir, char *string) {
int dx,dy;
if(!dir) dir=pl->facing;
dx=freearr_x[dir],dy=freearr_y[dir];
/* If we don't yet have an opponent, find if one exists, and attack.
* Legal opponents are the same as outlined in move_player_attack()
*/
if(tmp==NULL)
for(tmp=get_map_ob(pl->map,pl->x+dx,pl->y+dy);tmp;tmp=tmp->above)
if((QUERY_FLAG(tmp,FLAG_ALIVE) && tmp->stats.hp>=0)
|| QUERY_FLAG(tmp, FLAG_CAN_ROLL)
|| tmp->type==LOCKED_DOOR ) {
#ifdef SIMPLE_PARTY_SYSTEM
if((pl->type==PLAYER && tmp->type==PLAYER) && (pl->contr->party_number!=-1
&& pl->contr->party_number==tmp->contr->party_number))
return 0;
#endif
break;
}
if(tmp!=NULL) return do_skill_attack(tmp,pl,string);
if(pl->type==PLAYER)
new_draw_info(NDI_UNIQUE, 0,pl,"There is nothing to attack!");
return 0;
}
/* do_skill_attack() - We have got an appropriate opponent from either
* move_player_attack() or skill_attack(). In this part we get on with
* attacking, take care of messages from the attack and changes in invisible.
* Returns true if the attack damaged the opponent.
* -b.t. thomas@astro.psu.edu
*/
int do_skill_attack(object *tmp, object *op, char *string) {
int success;
object *pskill = op->chosen_skill;
char buf[MAX_BUF], *name = query_name(tmp);
char *attack_hth_skill[] = {"skill_karate", "skill_clawing",
"skill_flame_touch", "skill_punching", NULL };
/* For Players only: if there is no ready weapon, and no "attack" skill
* is readied either then check if op has a hand-to-hand "attack skill"
* to use instead. We define this an "attack" skill as any skill
* that is linked to the strexpobj.
* If we are using the wrong skill, change it (if possible) to
* a more appropriate skill (in order from the attack_hth_skill[]
* list). If the miserable slob still does'nt have any of the hth
* skills, give them the last attack_hth_skill on the list. There
* is probably a better way to do this...how??
*/
if(op->type==PLAYER
&& !QUERY_FLAG(op,FLAG_READY_WEAPON)
&& ((!pskill||!pskill->exp_obj)||!pskill->exp_obj->stats.Str)) {
int i=0,got_one=0;
object *tmp2=NULL;
while(attack_hth_skill[i]!=NULL) {
for(tmp2=op->inv;tmp2;tmp2=tmp2->below)
if(tmp2->type==SKILL
&& !strcmp(attack_hth_skill[i],tmp2->arch->name)) {
got_one=1;
break;
}
if(got_one) break;
i++;
}
if(!got_one) { /* Arrgh. The player has no hth attack skills in inventory.
* Lets give them the last one in attack_hth_skill[] */
archetype *skill;
skill=find_archetype(attack_hth_skill[i-1]);
if(!skill) {
LOG(llevError,"do_skill_attack() could'nt find attack skill for %s\n",
op->name);
return 0;
}
tmp2=arch_to_object(skill);
insert_ob_in_ob(tmp2,op);
(void) link_player_skill(op,tmp2);
if (op->contr->eric_server > 0)
esrv_send_item(op, tmp2);
}
/* now try to ready the new skill */
if(!change_skill(op,tmp2->stats.sp)) { /* oh oh, trouble! */
LOG(llevError,"do_skill_attack() could'nt give new hth skill to %s\n",
op->name);
return 0;
}
}
/* check if op is using a hth attack, if so modify damage */
if(QUERY_FLAG(op,FLAG_READY_SKILL) && op->chosen_skill
&& !QUERY_FLAG(op, FLAG_READY_WEAPON))
if((op->type!=PLAYER) || (op->type==PLAYER
&& op->contr->shoottype==range_skill)) {
int dam=hth_damage(tmp,op);
if(dam!=op->stats.dam) {
op->stats.dam=dam;
draw_stats(op);
}
}
/* if we have 'ready weapon' but no 'melee weapons' skill readied
* this will flip to that skill. This is only window dressing for
* the players--no need to do this for monsters.
*/
if(op->type==PLAYER && QUERY_FLAG(op,FLAG_READY_WEAPON)
&& (!op->chosen_skill || op->chosen_skill->stats.sp!=SK_MELEE_WEAPON)) {
#ifdef NO_AUTO_SKILL_SWITCH
rangetype oldrange=op->contr->shoottype;
#endif
(void) change_skill(op,SK_MELEE_WEAPON);
/* This is just a simple hack - would probably be cleaner to have change_skill
* do the right thing, but this isn't that bad.
*/
#ifdef NO_AUTO_SKILL_SWITCH
if (op->contr->shoottype!=oldrange) {
op->contr->shoottype=oldrange;
draw_stats(op);
}
#endif
}
/* lose invisiblity/hiding status for running attacks */
if(op->type==PLAYER && op->contr->tmp_invis) {
op->contr->tmp_invis=0;
op->invisible=0;
op->hide=0;
update_object(op);
}
success = attack_ob(tmp,op);
/* print appropriate messages to the player */
if(success && string!=NULL) {
sprintf(buf, string);
if(op->type==PLAYER)
new_draw_info_format(NDI_UNIQUE, 0,op,
"You %s %s!",buf,name);
else if(tmp->type==PLAYER)
new_draw_info_format(NDI_UNIQUE, 0,tmp,
"%s %s you!",query_name(op),buf);
}
return success;
}
/* hth_damage() - returns the appropriate amount of hth damage, based on the
* skill in use and the opponent's characteristics. This is generalized to
* allow monster skill use too. Called only from do_skill_attack(). -b.t.
*/
int hth_damage(object *target, object *pl) {
int base = pl->arch->clone.stats.dam + dam_bonus[pl->stats.Str];
int damage = pl->chosen_skill->stats.dam * (1 + (SK_level(pl)/9));
if(damage<1) /* Safety - not a hth skill per se */
return base;
else
damage += base;
/* are we using a skill?, if not return normal hth damage */
if(pl->chosen_skill==NULL
|| !QUERY_FLAG(pl,FLAG_READY_SKILL)) return damage;
/* Against some creatures the hth attack gets no special bonus.
* Probably left out some cases here -b.t.
*/
if((target->level>SK_level(pl))
|| QUERY_FLAG(target,FLAG_UNDEAD)
|| QUERY_FLAG(target,FLAG_SPLITTING)
|| QUERY_FLAG(target,FLAG_HITBACK)) {
pl->attacktype=AT_PHYSICAL;
return damage;
}
damage += (((SK_level(pl)/18)+1)*pl->chosen_skill->stats.dam) + 1;
return damage;
}
/* This is in the same spirit as the similar routine for spells
* it should be used anytime a function needs to check the user's
* level.
*/
int SK_level(object *op) {
int level = op->level;
#ifdef ALLOW_SKILLS
if(op->type==PLAYER && op->chosen_skill && op->chosen_skill->level!=0) {
level = op->chosen_skill->level;
}
#endif
if(level<=0) level = 1; /* safety */
return level;
}
/* returns the amount of time it takes to use a skill.
* We allow for stats, and level to modify the amount
* of time. Monsters have no skill use time because of
* the random nature in which use_monster_skill is called
* already simulates this. -b.t.
*/
int get_skill_time(object *op, int skillnr) {
int time = skills[skillnr].time;
if(!time || op->type!=PLAYER)
return 0;
else {
int sum = get_weighted_skill_stat_sum (op,skillnr);
int level = SK_level(op)/10;
time *= 1/(1+(sum/15)+level);
}
return FABS(time);
}
/* get_skill_stat1() - returns the value of the primary skill
* stat. Used in various skills code. -b.t.
*/
int get_skill_stat1(object *op) {
int stat_value = 0, stat=NO_STAT;
if((op->chosen_skill) && ((stat = skills[op->chosen_skill->stats.sp].stat1)!=NO_STAT) )
stat_value = get_attr_value(&(op->stats),stat);
return stat_value;
}
/* get_skill_stat2() - returns the value of the secondary skill
* stat. Used in various skills code. -b.t.
*/
int get_skill_stat2(object *op) {
int stat_value = 0,stat = NO_STAT;
if((op->chosen_skill) && ((stat = skills[op->chosen_skill->stats.sp].stat2)!=NO_STAT) )
stat_value = get_attr_value(&(op->stats),stat);
return stat_value;
}
/* get_skill_stat3() - returns the value of the tertiary skill
* stat. Used in various skills code. -b.t.
*/
int get_skill_stat3(object *op) {
int stat_value = 0,stat = NO_STAT;
if((op->chosen_skill) && ((stat = skills[op->chosen_skill->stats.sp].stat3)!=NO_STAT) )
stat_value = get_attr_value(&(op->stats),stat);
return stat_value;
}
/*get_weighted_skill_stats() - */
int get_weighted_skill_stats(object *op) {
int value=0;
value = (get_skill_stat1(op)/2)+(get_skill_stat2(op)/4)+(get_skill_stat3(op)/4);
return value;
}
/* find_skill() - looks for the skill and returns a pointer to it if found */
object *find_skill(object *op, int skillnr) {
object *tmp;
#ifdef LINKED_SKILL_LIST
objectlink *obl;
if(op->sk_list!=NULL) { /* use fast method */
for(obl=op->sk_list;obl;obl=obl->next)
if(obl->id==skillnr&&obl->ob) return obl->ob;
}
#endif
/* *sigh*; we didnt find it. Perhaps because the skill we requested
* is an unapplied tool. Lets search entire inventory */
for(tmp=op->inv;tmp;tmp=tmp->below)
if(tmp->type==SKILL&&tmp->stats.sp==skillnr)
return tmp;
return ((object *) NULL);
}